package com.urbanski.util;

/**
 * A collection of String utility methods
 * 
 * @author Mike Urbanski
 * 
 */
public class StringUtils
{
	/**
	 * Pads spaces on to the end of s until s.length() == l. If the string is longer than l,
	 * truncates to that length
	 * 
	 * @param s
	 *            the string to pad or truncate
	 * @param l
	 *            the length of the string
	 * @return a string of length l
	 */
	public static String fixLengthAfter(String s, int l)
	{
		return fixLengthAfter(s, l, ' ');
	}

	/**
	 * Pads spaces on to the end of i until length == l. If the integer is longer than l, truncates
	 * to that length
	 * 
	 * @param i
	 *            the integer to pad or truncate
	 * @param l
	 *            the length of the string
	 * @return a string of length l
	 */
	public static String fixLengthAfter(int i, int l)
	{
		return fixLengthAfter("" + i, l);
	}

	/**
	 * Pads the character c on to the end of s until s.length() = l. If the string is longer than l,
	 * truncates to that length
	 * 
	 * @param s
	 *            the string to pad or truncate
	 * @param l
	 *            the length of the string
	 * @param c
	 *            the character to append
	 * @return a string of length l
	 */
	public static String fixLengthAfter(String s, int l, char c)
	{
		if (s == null)
			s = "";
		if (s.length() >= l)
			return s.substring(0, l);
		StringBuffer sb = new StringBuffer(l);
		sb.append(s);
		while (sb.length() < l)
			sb.append(c);
		return sb.toString();
	}

	/**
	 * Pads the character c on to the end of i until length == l. If the integer is longer than l,
	 * truncates to that length
	 * 
	 * @param i
	 *            the integer to pad or truncate
	 * @param l
	 *            the length of the string
	 * @param c
	 *            the character to append
	 * @return a string of length l
	 */
	public static String fixLengthAfter(int i, int l, char c)
	{
		return fixLengthAfter("" + i, l, c);
	}

	/**
	 * Pads spaces on to the beginning of s until s.length() = l. If the string is longer than l,
	 * truncates to that length
	 * 
	 * @param s
	 *            the string to pad or truncate
	 * @param l
	 *            the length of the string
	 * @return a string of length l
	 */
	public static String fixLengthBefore(String s, int l)
	{
		return fixLengthBefore(s, l, ' ');
	}

	/**
	 * Pads spaces on to the beginning of i until length == l. If the integer is longer than l,
	 * truncates to that length
	 * 
	 * @param i
	 *            the integer to pad or truncate
	 * @param l
	 *            the length of the string
	 * @return a string of length l
	 */
	public static String fixLengthBefore(int i, int l)
	{
		return fixLengthBefore("" + i, l, ' ');
	}

	/**
	 * Pads the character c on to the beginning of s until s.length() = l. If the string is longer
	 * than l, truncates to that length
	 * 
	 * @param s
	 *            the string to pad or truncate
	 * @param l
	 *            the length of the string
	 * @param c
	 *            the character to append
	 * @return a string of length l
	 */
	public static String fixLengthBefore(String s, int l, char c)
	{
		if (s == null)
			s = "";
		if (s.length() >= l)
			return s.substring(0, l);
		StringBuffer sb = new StringBuffer(l);
		while (l-- > s.length())
			sb.append(c);
		sb.append(s);
		return sb.toString();
	}

	/**
	 * Pads the character c on to the beginning of i until length == l. If the integer is longer
	 * than l, truncates to that length
	 * 
	 * @param i
	 *            the integer to pad or truncate
	 * @param l
	 *            the length of the string
	 * @param c
	 *            the character to append
	 * @return a string of length l
	 */
	public static String fixLengthBefore(int i, int l, char c)
	{
		return fixLengthBefore("" + i, l, c);
	}

	/**
	 * Computes the LCS of the two strings
	 * 
	 * @param s1
	 *            the first string to compare
	 * @param s2
	 *            the second string to compare
	 * @return the length of the longest common sequence of the two strings
	 */
	public static int LCS(String s1, String s2)
	{
		int[][] c = new int[s1.length() + 1][s2.length() + 1];

		for (int i = 1; i <= s1.length(); i++)
		{
			for (int j = 1; j <= s2.length(); j++)
			{
				if (s1.charAt(i - 1) == s2.charAt(j - 1))
				{
					c[i][j] = c[i - 1][j - 1] + 1;
				}
				else
				{
					c[i][j] = Math.max(c[i][j - 1], c[i - 1][j]);
				}
			}
		}
		printDiff(c, s1, s2, s1.length(), s2.length());
		return c[c.length - 1][c[c.length - 1].length - 1];
	}

	/**
	 * Prints the diff between two strings using a previously computed LCS matrix
	 * 
	 * @param c
	 *            the matrix created in computing the LCS of these two strings
	 * @param s1
	 *            the first string to compare
	 * @param s2
	 *            the second string to compare
	 * @param i
	 *            the starting character in the first string for comparison (1-based)
	 * @param j
	 *            the starting character in the second string for comparison (1-based)
	 */
	public static void printDiff(int[][] c, String s1, String s2, int i, int j)
	{
		if (i > 0 && j > 0 && s1.charAt(i - 1) == s2.charAt(j - 1))
		{
			printDiff(c, s1, s2, i - 1, j - 1);
			System.out.print(" " + s1.charAt(i - 1));
		}
		else if (j > 0 && (i == 0 || c[i][j - 1] >= c[i - 1][j]))
		{
			printDiff(c, s1, s2, i, j - 1);
			System.out.print(" +" + s2.charAt(j - 1));
		}
		else if (i > 0 && (j == 0 || c[i][j - 1] < c[i - 1][j]))
		{
			printDiff(c, s1, s2, i - 1, j);
			System.out.print(" -" + s1.charAt(i - 1));
		}
	}
	
	/**
	 * Trims all Strings in the specified array
	 * 
	 * @param arr the array of Strings to trim
	 */
	public static void trimAll(String[] arr)
	{
		for (int i = 0; i < arr.length; i++)
		{
			arr[i] = arr[i].trim();
		}
	}
	
	/**
	 * Determines whether a String is in a set of Strings, ignoring case.  Behaves analogous to the SQL
	 * where in clause.
	 * 
	 * @param string the String to check
	 * @param list the Strings to search
	 * @return true of String is in the list of Strings, or false otherwise.
	 */
	public static boolean containsIgnoreCase(String string, String... list)
	{
		for (String o : list)
		{
			if (string.equalsIgnoreCase(o))
			{
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Parses an array of Strings that contain int values into an int array
	 * 
	 * @param arr an array of Strings containing int values
	 * @return an array of ints, in the same order and length of arr, with the values
	 * parsed from String to int
	 * @throws NumberFormatException if an element of the array cannot be parsed to an int
	 */
	public static int[] parseIntArray(String[] arr)
	{
		int[] ret = new int[arr.length];
		for (int i = 0; i < arr.length; i++)
		{
			ret[i] = Integer.parseInt(arr[i]);
		}
		return ret;
	}
	
	/**
	 * Truncates the specified String to the specified length adding ellipses
	 * (...).  If the specified String is less than or equal to the specified
	 * length, then the String is returned outright.  If it is greater than
	 * the specified length, then it is truncated to a length of length - 3,
	 * and an ellipses is added on.  If the string longer than the specified
	 * length, the returned string's length will always equal length.
	 * 
	 * @param string the String to truncate
	 * @param length the maximum length of the returned String
	 * @return a String truncated to no longer than the specified length, with
	 * ellipses added as necessary
	 * @throws NullPointerException if string is null
	 * @throws IllegalArgumentException if the length is negative,
	 * or if length < 3 but larger than the length of the string 
	 */
	public static String ellipses(String string, int length)
	{
		if (string.length() <= length)
		{
			return string;
		}
		else if (length < 3)
		{
			throw new IllegalArgumentException("Length parameter was not large enough to hold ellipses");
		}
		else
		{
			return string.substring(0, length - 3) + "...";
		}
	}
	
	/**
	 * Truncates the specified string down to the specified length.  If
	 * the String's length is less than or equal to length, then
	 * the String is returned as is.
	 * 
	 * @param string the string to truncate
	 * @param length the maximum length of the returned string
	 * @return the specified string truncated to the specified length
	 * @throws NullPointerException if string is null
	 * @throws IllegalArgumentException if length is negative
	 */
	public static String truncate(String string, int length)
	{
		if (string.length() <= length)
		{
			return string;
		}
		else if (length < 0)
		{
			throw new IllegalArgumentException("Length parameter cannot be less than 0");
		}
		else
		{
			return string.substring(0, length);
		}
	}
	
	/**
	 * Checks whether the specified String is null or empty.  The String is
	 * empty if s.length() equals 0.
	 * 
	 * @param s the String to check
	 * @return true if the specified String is null or has a length of 0
	 */
	public static boolean isNullOrEmpty(String s)
	{
		return s == null || s.length() == 0;
	}
	/**
	 * Checks whether the specified String is null or empty, optionally trimming
	 * the String.  If trim is false, then this method behaves identically to
	 * isNullOrEmpty(String).  If trim is true, then the String is trimmed
	 * (assuming it is not null) before checking whether its length is 0.
	 * 
	 * @param s the String to check
	 * @param trim whether to trim the String before checking its length
	 * @return true if the specified String is null or has a length of 0,
	 * possibly after trimming
	 */
	public static boolean isNullOrEmpty(String s, boolean trim)
	{
		return s == null || (trim ? s.trim() : s).length() == 0;
	}
	
	/**
	 * Concatenates a number of characters into a String
	 * @param cs the characters to concatenate into a String
	 * @return the specified characters concatenated into a String
	 */
	public static String concat(char... cs)
	{
		return new String(cs);
	}
	
	/**
	 * Checks whether the specified String contains only Latin numbers (0-9).
	 * Returns false if the String is null, empty, or contains non-numeric
	 * characters (including leading or trailing whitespace).
	 * @param s the String to check
	 * @return true if the String contains only the numbers 0-9.
	 */
	public static boolean isNumeric(String s)
	{
		if (s == null || s.length() == 0)
			return false;
		
		for (char c : s.toCharArray())
		{
			if (c < '0' || c > '9')
				return false;
		}
		return true;
	}
	
	/**
	 * Checks whether the specified String is a valid number.  A valid number
	 * is a String that contains all digits, except for, optionally, a single
	 * minus sign at the beginning and, also optionally, a single decimal
	 * point at any point in the String (except before the minus sign).
	 * 
	 * Note that this method does not perform any range checking - for example,
	 * the String may not be a valid Integer if it is too large.
	 * 
	 * Returns false if the String is null or empty.
	 * 
	 * @param s the String to check 
	 * @return true if the specified String is a valid number
	 */
	public static boolean isNumber(String s)
	{
		if (s == null || s.length() == 0)
			return false;
		else if (s.length() == 1)
		{
			char c = s.charAt(0);
			return c >= '0' && c <= '9';
		}
		else
		{
			boolean canBeMinus = true;
			boolean canBeDecimal = true;

			for (char c : s.toCharArray())
			{
				if (c == '-' && !canBeMinus)
					return false;
				else if (c == '-')
					canBeMinus = false;
				else if (c == '.' && !canBeDecimal)
					return false;
				else if (c == '.')
				{
					canBeDecimal = false;
				}
				else
				{
					if (c < '0' || c > '9')
						return false;
				}
				
				canBeMinus = false;
			}
		}
		
		return true;
	}
	
	
}
